import type { ImportDeclaration } from 'acorn'
import type { TransformPluginContext } from 'rollup'
import { simple } from 'acorn-walk'
import { minify_sync } from 'terser'

interface FunctionDeclaration {
  // 模块
  id: string
  // 函数名
  statement: string[]
}

export class ImportsProcessor {
  imports: FunctionDeclaration[] = []

  context: TransformPluginContext

  // 已load的依赖，避免重复load
  loaded: Map<string, string> = new Map()

  constructor(context: TransformPluginContext) {
    this.context = context
  }

  async loadModule(id: string) {
    const resolve = await this.context.resolve(id)
    return await this.context.load({ id: resolve?.id || id })
  }

  // 根据函数名称解析对应的文件或模块
  async findModuleByFnName(fnName: string): Promise<string | null> {
    // 查找函数所属文件或模块
    const importDeclaration = this.imports.find(imp =>
      imp.statement.includes(fnName),
    )
    if (!importDeclaration)
      return null

    // 加载文件内容
    const sourceCode
      = this.loaded.get(importDeclaration.id)
        || (await this.loadModule(importDeclaration.id)).code
    if (!sourceCode)
      return null
    // 缓存已加载的模块代码
    this.loaded.set(importDeclaration.id, sourceCode)
    // 返回模块代码
    return sourceCode
  }

  // 根据代码和函数名称找到函数的声明代码
  findFnDeclarationByCode(code: string, fnName: string): string | null {
    // 解析AST
    const ast = this.context.parse(code)
    // 函数声明代码
    let fnDeclaration: string | null = null
    // 遍历AST
    simple(ast, {
      FunctionDeclaration: (node) => {
        if (node.id?.name === fnName) {
          // fnDeclaration = transformSync()
          const { code: minifyCode } = minify_sync(
            code.slice(node.start, node.end),
            { format: { comments: false } },
          )
          fnDeclaration = minifyCode || null
        }
        return fnDeclaration !== null
      },
    })

    return fnDeclaration
  }

  // 根据函数名称找到函数的声明代码
  async findFnDeclarationByFnName(fnName: string): Promise<string | null> {
    const moduleCode = await this.findModuleByFnName(fnName)
    if (!moduleCode)
      return null

    return this.findFnDeclarationByCode(moduleCode, fnName)
  }

  // 追加引入
  append(node: ImportDeclaration) {
    const raw = node.source.raw!
    this.imports.push({
      id: raw?.slice(1, raw.length - 1),
      statement: node.specifiers.map(specifier => specifier.local.name),
    })
  }
}
